import * as React from 'react';
import {findDOMNode} from 'react-dom';
import {Renderer, RendererProps} from '../factory';
import {SchemaNode, Action} from '../types';
import * as cx from 'classnames';
import Button from '../components/Button';
import {ListStore, IListStore, IItem} from '../store/list';
import {observer} from 'mobx-react';
import {anyChanged, getScrollParent, difference, ucFirst} from '../utils/helper';
import {resolveVariable} from '../utils/tpl-builtin';
import Sortable = require('sortablejs');
import {filter} from '../utils/tpl';
import debounce = require('lodash/debounce');
import {resizeSensor} from '../utils/resize-sensor';

export interface Column {
    type: string;
    [propName: string]: any;
}

export interface GridProps extends RendererProps {
    title?: string; // 标题
    header?: SchemaNode;
    body?: SchemaNode;
    footer?: SchemaNode;
    store: IListStore;
    className?: string;
    headerClassName?: string;
    footerClassName?: string;
    itemClassName?: string;
    card?: any;
    source?: string;
    selectable?: boolean;
    selected?: Array<any>;
    multiple?: boolean;
    valueField?: string;
    draggable?: boolean;
    onSelect: (selectedItems: Array<object>, unSelectedItems: Array<object>) => void;
    onSave?: (
        items: Array<object> | object,
        diff: Array<object> | object,
        rowIndexes: Array<number> | number,
        unModifiedItems?: Array<object>
    ) => void;
    onSaveOrder?: (moved: Array<object>, items: Array<object>) => void;
    onQuery: (values: object) => void;
    hideCheckToggler?: boolean;
    itemCheckableOn?: string;
    itemDraggableOn?: string;
    checkOnItemClick?: boolean;
    masonryLayout?: boolean;
}

export default class Cards extends React.Component<GridProps, object> {
    static propsList: Array<string> = [
        'header',
        'headerToolbarRender',
        'footer',
        'footerToolbarRender',
        'placeholder',
        'source',
        'selectable',
        'headerClassName',
        'footerClassName',
        'fixAlignment',
        'hideQuickSaveBtn',
        'hideCheckToggler',
        'itemCheckableOn',
        'itemDraggableOn',
        'masonryLayout',
        'items',
        'valueField',
    ];
    static defaultProps: Partial<GridProps> = {
        className: '',
        placeholder: '没有数据',
        source: '$items',
        selectable: false,
        headerClassName: '',
        footerClassName: '',
        itemClassName: 'Grid-col--sm6 Grid-col--md4 Grid-col--lg3',
        // fixAlignment: false,
        hideCheckToggler: false,
        masonryLayout: false,
        affixHeader: true,
        itemsClassName: '',
    };

    dragTip?: HTMLElement;
    sortable?: Sortable;
    parentNode?: any;
    body?: any;
    // fixAlignmentLazy: Function;
    unSensor: Function;
    constructor(props: GridProps) {
        super(props);

        this.handleAction = this.handleAction.bind(this);
        this.handleCheck = this.handleCheck.bind(this);
        this.handleCheckAll = this.handleCheckAll.bind(this);
        this.handleQuickChange = this.handleQuickChange.bind(this);
        this.handleSave = this.handleSave.bind(this);
        this.handleSaveOrder = this.handleSaveOrder.bind(this);
        this.reset = this.reset.bind(this);
        this.dragTipRef = this.dragTipRef.bind(this);
        this.bodyRef = this.bodyRef.bind(this);
        this.affixDetect = this.affixDetect.bind(this);
        this.itemsRef = this.itemsRef.bind(this);
        // this.fixAlignmentLazy = debounce(this.fixAlignment.bind(this), 250, {
        //     trailing: true,
        //     leading: false
        // })
    }

    static syncItems(store: IListStore, props: GridProps, prevProps?: GridProps) {
        const source = props.source;
        const value = props.value || props.items;
        let items: Array<object> = [];
        let updateItems = true;

        if (Array.isArray(value)) {
            items = value;
        } else if (typeof source === 'string') {
            const resolved = resolveVariable(source, props.data);
            const prev = prevProps ? resolveVariable(source, prevProps.data) : null;

            if (prev && prev === resolved) {
                updateItems = false;
            } else if (Array.isArray(resolved)) {
                items = resolved;
            }
        }

        updateItems && store.initItems(items);
        typeof props.selected !== 'undefined' && store.updateSelected(props.selected, props.valueField);
    }

    componentWillMount() {
        const {
            store,
            selectable,
            draggable,
            orderBy,
            orderDir,
            multiple,
            hideCheckToggler,
            itemCheckableOn,
            itemDraggableOn,
        } = this.props;

        store.update({
            selectable,
            draggable,
            orderBy,
            orderDir,
            multiple,
            hideCheckToggler,
            itemCheckableOn,
            itemDraggableOn,
        });

        Cards.syncItems(store, this.props);
        this.syncSelected();
    }

    componentDidMount() {
        let parent: HTMLElement | Window | null = getScrollParent(findDOMNode(this) as HTMLElement);
        if (!parent || parent === document.body) {
            parent = window;
        }

        this.parentNode = parent;
        this.affixDetect();
        parent.addEventListener('scroll', this.affixDetect);
        window.addEventListener('resize', this.affixDetect);
    }

    componentWillReceiveProps(nextProps: GridProps) {
        const props = this.props;
        const store = nextProps.store;

        if (
            anyChanged(
                [
                    'selectable',
                    'draggable',
                    'orderBy',
                    'orderDir',
                    'multiple',
                    'hideCheckToggler',
                    'itemCheckableOn',
                    'itemDraggableOn',
                ],
                props,
                nextProps
            )
        ) {
            store.update({
                selectable: nextProps.selectable,
                draggable: nextProps.draggable,
                orderBy: nextProps.orderBy,
                orderDir: nextProps.orderDir,
                multiple: nextProps.multiple,
                hideCheckToggler: nextProps.hideCheckToggler,
                itemCheckableOn: nextProps.itemCheckableOn,
                itemDraggableOn: nextProps.itemDraggableOn,
            });
        }

        if (
            anyChanged(['source', 'value', 'items'], props, nextProps) ||
            (!nextProps.value && !nextProps.items && nextProps.data !== props.data)
        ) {
            Cards.syncItems(store, nextProps, props);
            this.syncSelected();
        } else if (props.selected !== nextProps.selected) {
            store.updateSelected(nextProps.selected || [], nextProps.valueField);
        }
    }

    componentWillUnmount() {
        const parent = this.parentNode;
        parent && parent.removeEventListener('scroll', this.affixDetect);
        window.removeEventListener('resize', this.affixDetect);
    }

    // fixAlignment() {
    //     if (!this.props.fixAlignment || this.props.masonryLayout) {
    //         return;
    //     }

    //     const dom = this.body as HTMLElement;
    //     const ns = this.props.classPrefix;
    //     const cards = [].slice.apply(dom.querySelectorAll(`.${ns}Cards-body > div`));

    //     if (!cards.length) {
    //         return;
    //     }

    //     let maxHeight = cards.reduce((maxHeight:number, item:HTMLElement) => Math.max(item.offsetHeight, maxHeight), 0);
    //     cards.forEach((item: HTMLElement) => item.style.cssText += `min-height: ${maxHeight}px;`);
    // }

    bodyRef(ref: HTMLDivElement) {
        this.body = ref;
    }

    itemsRef(ref: HTMLDivElement) {
        if (ref) {
            // this.unSensor = resizeSensor(ref.parentNode as HTMLElement, this.fixAlignmentLazy);
        } else {
            this.unSensor && this.unSensor();
            delete this.unSensor;
        }
    }

    affixDetect() {
        if (!this.props.affixHeader || !this.body) {
            return;
        }

        const ns = this.props.classPrefix;
        const dom = findDOMNode(this) as HTMLElement;
        const clip = (this.body as HTMLElement).getBoundingClientRect();
        const offsetY = this.props.env.affixOffsetTop || 0;
        const affixed = clip.top < offsetY && clip.top + clip.height - 40 > offsetY;
        const afixedDom = dom.querySelector(`.${ns}Cards-fixedTop`) as HTMLElement;

        this.body.offsetWidth && (afixedDom.style.cssText = `top: ${offsetY}px;width: ${this.body.offsetWidth}px;`);
        affixed ? afixedDom.classList.add('in') : afixedDom.classList.remove('in');
        // store.markHeaderAffix(clip.top < offsetY && (clip.top + clip.height - 40) > offsetY);
    }

    handleAction(e: React.UIEvent<any>, action: Action, ctx: object) {
        const {onAction} = this.props;

        // 需要支持特殊事件吗？
        onAction(e, action, ctx);
    }

    handleCheck(item: IItem) {
        item.toggle();
        this.syncSelected();
    }

    handleCheckAll() {
        const {store} = this.props;

        store.toggleAll();
        this.syncSelected();
    }

    syncSelected() {
        const {store, onSelect} = this.props;

        onSelect && onSelect(store.selectedItems.map(item => item.data), store.unSelectedItems.map(item => item.data));
    }

    handleQuickChange(item: IItem, values: object, saveImmediately?: boolean | any, saveSilent?: boolean) {
        item.change(values, saveSilent);

        if (!saveImmediately || saveSilent) {
            return;
        }

        if (saveImmediately && saveImmediately.api) {
            this.props.onAction(
                null,
                {
                    actionType: 'ajax',
                    api: saveImmediately.api,
                },
                values
            );
            return;
        }

        const {onSave} = this.props;

        if (!onSave) {
            return;
        }

        onSave(item.data, difference(item.data, item.pristine), item.index);
    }

    handleSave() {
        const {store, onSave} = this.props;

        if (!onSave || !store.modifiedItems.length) {
            return;
        }

        const items = store.modifiedItems.map(item => item.data);
        const itemIndexes = store.modifiedItems.map(item => item.index);
        const diff = store.modifiedItems.map(item => difference(item.data, item.pristine));
        const unModifiedItems = store.items.filter(item => !item.modified).map(item => item.data);
        onSave(items, diff, itemIndexes, unModifiedItems);
    }

    handleSaveOrder() {
        const {store, onSaveOrder} = this.props;

        if (!onSaveOrder || !store.movedItems.length) {
            return;
        }

        onSaveOrder(store.movedItems.map(item => item.data), store.items.map(item => item.data));
    }

    reset() {
        const {store} = this.props;

        store.reset();
    }

    bulkUpdate(value: object, items: Array<object>) {
        const {store} = this.props;

        const items2 = store.items.filter(item => ~items.indexOf(item.pristine));
        items2.forEach(item => item.change(value));
    }

    getSelected() {
        const {store} = this.props;

        return store.selectedItems.map(item => item.data);
    }

    dragTipRef(ref: any) {
        if (!this.dragTip && ref) {
            this.initDragging();
        } else if (this.dragTip && !ref) {
            this.destroyDragging();
        }

        this.dragTip = ref;
    }

    initDragging() {
        const store = this.props.store;
        const dom = findDOMNode(this) as HTMLElement;
        const ns = this.props.classPrefix;
        this.sortable = new Sortable(dom.querySelector(`.${ns}Cards-body`) as HTMLElement, {
            group: 'table',
            handle: `.${ns}Card-dragBtn`,
            ghostClass: `is-dragging`,
            onEnd: (e: any) => {
                // 没有移动
                if (e.newIndex === e.oldIndex) {
                    return;
                }

                const parent = e.to as HTMLElement;
                if (e.oldIndex < parent.childNodes.length - 1) {
                    parent.insertBefore(e.item, parent.childNodes[e.oldIndex]);
                } else {
                    parent.appendChild(e.item);
                }

                store.exchange(e.oldIndex, e.newIndex);
            },
        });
    }

    destroyDragging() {
        this.sortable && this.sortable.destroy();
    }

    renderActions(region: string) {
        let {actions, render, store, multiple, selectable, classnames: cx, classPrefix: ns, env} = this.props;

        actions = Array.isArray(actions) ? actions.concat() : [];

        if (store.selectable && multiple && selectable && !store.dragging && store.items.length) {
            actions.unshift({
                type: 'button',
                children: (
                    <Button
                        key="checkall"
                        classPrefix={ns}
                        tooltip="切换全选"
                        onClick={this.handleCheckAll}
                        size="sm"
                        level={store.allChecked ? 'info' : 'default'}
                    >
                        全选
                    </Button>
                ),
            });
        }

        if (store.draggable && region === 'header' && store.items.length > 1) {
            actions.unshift({
                type: 'button',
                children: (
                    <Button
                        iconOnly
                        classPrefix={ns}
                        key="dragging-toggle"
                        tooltip="对卡片进行排序操作"
                        tooltipContainer={env && env.getModalContainer ? env.getModalContainer() : undefined}
                        size="sm"
                        active={store.dragging}
                        onClick={(e: React.MouseEvent<any>) => {
                            e.preventDefault();
                            store.toggleDragging();
                            store.dragging && store.clear();
                        }}
                    >
                        <i className="fa fa-exchange" />
                    </Button>
                ),
            });
        }

        return Array.isArray(actions) && actions.length ? (
            <div className={cx('Cards-actions')}>
                {actions.map((action, key) =>
                    render(
                        `action/${key}`,
                        {
                            type: 'button',
                            ...action,
                        },
                        {
                            onAction: this.handleAction,
                            key,
                            btnDisabled: store.dragging,
                        }
                    )
                )}
            </div>
        ) : null;
    }

    renderHeading() {
        let {title, store, hideQuickSaveBtn, classnames: cx, data} = this.props;

        if (title || (store.modified && !hideQuickSaveBtn) || store.moved) {
            return (
                <div className={cx('Cards-heading')}>
                    {store.modified && !hideQuickSaveBtn ? (
                        <span>
                            {`当前有 ${store.modified} 条记录修改了内容, 但并没有提交。请选择:`}
                            <button
                                type="button"
                                className={cx('Button Button--xs Button--success m-l-sm')}
                                onClick={this.handleSave}
                            >
                                <i className="fa fa-check m-r-xs" />
                                提交
                            </button>
                            <button
                                type="button"
                                className={cx('Button Button--xs Button--danger m-l-sm')}
                                onClick={this.reset}
                            >
                                <i className="fa fa-times m-r-xs" />
                                放弃
                            </button>
                        </span>
                    ) : store.moved ? (
                        <span>
                            {`当前有 ${store.moved} 条记录修改了顺序, 但并没有提交。请选择:`}
                            <button
                                type="button"
                                className={cx('Button Button--xs Button--success m-l-sm')}
                                onClick={this.handleSaveOrder}
                            >
                                <i className="fa fa-check m-r-xs" />
                                提交
                            </button>
                            <button
                                type="button"
                                className={cx('Button Button--xs Button--danger m-l-sm')}
                                onClick={this.reset}
                            >
                                <i className="fa fa-times m-r-xs" />
                                放弃
                            </button>
                        </span>
                    ) : title ? (
                        filter(title, data)
                    ) : (
                        ''
                    )}
                </div>
            );
        }

        return null;
    }

    renderHeader() {
        const {
            header,
            headerClassName,
            headerToolbar,
            headerToolbarRender,
            showHeader,
            render,
            store,
            classnames: cx,
        } = this.props;

        if (showHeader === false) {
            return null;
        }

        const actions = this.renderActions('header');
        const child = headerToolbarRender
            ? headerToolbarRender({
                  ...this.props,
                  selectedItems: store.selectedItems.map(item => item.data),
                  items: store.items.map(item => item.data),
                  unSelectedItems: store.unSelectedItems.map(item => item.data),
              })
            : null;
        const toolbarNode =
            actions || child || store.dragging ? (
                <div className={cx('Cards-toolbar')} key="header-toolbar">
                    {actions}
                    {child}
                    {store.dragging ? (
                        <div className={cx('Cards-dragTip')} ref={this.dragTipRef}>
                            请拖动右边的按钮进行排序
                        </div>
                    ) : null}
                </div>
            ) : null;
        const headerNode = header ? (
            <div className={cx('Cards-header', headerClassName)} key="header">
                {render('header', header)}
            </div>
        ) : null;
        return headerNode && toolbarNode ? [headerNode, toolbarNode] : headerNode || toolbarNode || null;
    }

    renderFooter() {
        const {
            footer,
            footerClassName,
            footerToolbar,
            footerToolbarRender,
            render,
            showFooter,
            store,
            classnames: cx,
        } = this.props;

        if (showFooter === false) {
            return null;
        }

        const actions = this.renderActions('footer');
        const child = footerToolbarRender
            ? footerToolbarRender({
                  ...this.props,
                  selectedItems: store.selectedItems.map(item => item.data),
                  items: store.items.map(item => item.data),
                  unSelectedItems: store.unSelectedItems.map(item => item.data),
              })
            : null;

        const toolbarNode =
            actions || child ? (
                <div className={cx('Cards-toolbar')} key="footer-toolbar">
                    {actions}
                    {child}
                </div>
            ) : null;
        const footerNode = footer ? (
            <div className={cx('Cards-footer', footerClassName)} key="footer">
                {render('footer', footer)}
            </div>
        ) : null;
        return footerNode && toolbarNode ? [toolbarNode, footerNode] : footerNode || toolbarNode || null;
    }

    render() {
        const {
            className,
            store,
            columnsCount,
            itemClassName,
            placeholder,
            render,
            affixHeader,
            card,
            onAction,
            multiple,
            hideCheckToggler,
            checkOnItemClick,
            masonryLayout,
            itemsClassName,
            classnames: cx,
        } = this.props;

        let itemFinalClassName: string = columnsCount
            ? `Grid-col--sm${Math.round(12 / columnsCount)}`
            : itemClassName || '';
        const header = this.renderHeader();
        const heading = this.renderHeading();
        const footer = this.renderFooter();
        let masonryClassName = '';

        if (masonryLayout) {
            masonryClassName =
                'Cards--masonry ' +
                itemFinalClassName
                    .split(/\s/)
                    .map(item => {
                        if (/^Grid-col--(xs|sm|md|lg)(\d+)/.test(item)) {
                            return `Cards--masonry${ucFirst(RegExp.$1)}${RegExp.$2}`;
                        }

                        return item;
                    })
                    .join(' ');
        }

        return (
            <div
                ref={this.bodyRef}
                className={cx('Cards', className, {
                    'Cards--unsaved': !!store.modified || !!store.moved,
                })}
            >
                {affixHeader ? (
                    <div className={cx('Cards-fixedTop')}>
                        {heading}
                        {header}
                    </div>
                ) : null}
                {heading}
                {header}
                {store.items.length ? (
                    <div ref={this.itemsRef} className={cx('Cards-body Grid', itemsClassName, masonryClassName)}>
                        {store.items.map((item, index) => {
                            return (
                                <div key={item.index} className={cx(itemFinalClassName)}>
                                    {render(
                                        `${index}`,
                                        {
                                            type: 'card',
                                            ...card,
                                        },
                                        {
                                            className: cx((card && card.className) || '', {
                                                'is-checked': item.checked,
                                                'is-modified': item.modified,
                                                'is-moved': item.moved,
                                            }),
                                            item,
                                            multiple,
                                            hideCheckToggler,
                                            selectable: store.selectable,
                                            checkable: item.checkable,
                                            draggable: item.draggable,
                                            selected: item.checked,
                                            onSelect: item.toggle,
                                            dragging: store.dragging,
                                            data: item.locals,
                                            checkOnItemClick,
                                            onAction,
                                            onCheck: this.handleCheck,
                                            onQuickChange: store.dragging ? null : this.handleQuickChange,
                                        }
                                    )}
                                </div>
                            );
                        })}
                    </div>
                ) : (
                    <div className={cx('Cards-placeholder')}>{placeholder}</div>
                )}

                {footer}
            </div>
        );
    }
}

@Renderer({
    test: /(^|\/)(?:crud\/body\/grid|cards)$/,
    name: 'cards',
    storeType: ListStore.name,
    weight: -100, // 默认的 grid 不是这样，这个只识别 crud 下面的 grid
})
export class CardsRenderer extends Cards {
    dragging: boolean;
    selectable: boolean;
    selected: boolean;
    onSelect: boolean;
    title?: string;
    subTitle?: string;
    desc?: string;
    avatar?: string;
    avatarClassName?: string;
    body?: SchemaNode;
    actions?: Array<Action>;
}
